Parsing JSON Responses Using Codable, Encodable, and Decodable in iOS
Course: iOS App Development
Topic: Parsing JSON Responses Using Codable, Encodable, and Decodable
Course Link: iOS App Development Training
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to JSON Parsing in iOS
Modern iOS applications frequently communicate with web servers to retrieve and submit information. Applications such as shopping platforms, social media apps, banking interfaces, and weather applications receive data from APIs in formats such as JSON (JavaScript Object Notation).
JSON is a lightweight data-interchange format that represents information using objects, arrays, strings, numbers, Boolean values, and null values. When an iOS application receives a JSON response, it must convert that data into Swift types before the application can conveniently use it.
Swift provides the Codable system to simplify this process. Codable combines the Encodable and Decodable protocols, allowing developers to convert Swift models into external representations and convert external representations back into Swift models.
Understanding these protocols helps developers write safer, cleaner, and more maintainable networking code.
2. Learning Objectives
- Understand JSON and its role in iOS application development.
- Learn the purpose of Codable, Encodable, and Decodable.
- Create Swift models that represent JSON data.
- Decode JSON API responses into Swift objects.
- Encode Swift objects into JSON request bodies.
- Handle nested objects, arrays, optional values, and custom keys.
- Configure JSONDecoder and JSONEncoder.
- Identify and troubleshoot common JSON parsing errors.
- Integrate JSON parsing with URLSession and REST APIs.
3. Understanding JSON Data
JSON is commonly used by REST APIs because it is readable, compact, and supported by most programming languages.
3.1 Example JSON Object
{
"id": 101,
"name": "Rahul Sharma",
"email": "[email protected]",
"age": 25,
"isActive": true
}
This JSON object represents a user with an identifier, name, email address, age, and account status.
3.2 Common JSON Data Types
| JSON Type |
Example |
Common Swift Representation |
| String |
"Rahul" |
String |
| Number |
25 |
Int, Double, or another numeric type |
| Boolean |
true |
Bool |
| Object |
{"city":"Mumbai"} |
A struct or class conforming to Decodable |
| Array |
[1, 2, 3] |
An Array of a compatible Swift type |
| Null |
null |
Usually an Optional Swift property |
JSON does not have a separate date type. Dates are generally transmitted as strings or numeric timestamps, and the application must configure or implement the appropriate conversion.
4. What Is Codable in Swift?
Codable is a Swift type alias that combines two protocols: Encodable and Decodable. It enables a type to be converted to and from an external representation, such as JSON.
typealias Codable = Decodable & Encodable
When a Swift structure contains supported property types and conforms to Codable, Swift can automatically generate much of the encoding and decoding implementation.
4.1 Example of a Codable Model
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
let age: Int
let isActive: Bool
}
The User structure represents the JSON object. Because the property names and compatible types match the JSON keys and values, Swift can synthesize the required encoding and decoding methods.
4.2 Advantages of Codable
- Reduces repetitive parsing code.
- Provides compile-time type checking for model definitions.
- Converts JSON into meaningful Swift models.
- Supports nested objects and arrays.
- Helps detect mismatches between JSON data and expected property types.
- Works with URLSession for API communication.
- Improves code readability and maintainability.
5. Understanding the Decodable Protocol
Decodable allows a Swift type to be initialized from an external representation. In iOS networking, it is commonly used to parse JSON responses returned by APIs.
For example, an application may request a list of products from a server. The response arrives as Data, and JSONDecoder converts it into an array of Product structures.
5.1 Defining a Decodable Model
import Foundation
struct Product: Decodable {
let id: Int
let title: String
let price: Double
let inStock: Bool
}
This model supports reading product information from JSON. It does not automatically provide the ability to encode a Product into JSON because it conforms only to Decodable.
5.2 Example JSON Response
{
"id": 201,
"title": "Wireless Headphones",
"price": 2499.99,
"inStock": true
}
5.3 Decoding JSON Data
import Foundation
let json = """
{
"id": 201,
"title": "Wireless Headphones",
"price": 2499.99,
"inStock": true
}
""".data(using: .utf8)!
do {
let product = try JSONDecoder().decode(
Product.self,
from: json
)
print(product.title)
print(product.price)
print(product.inStock)
} catch {
print("JSON decoding failed:", error)
}
5.4 Code Explanation
- JSON data: The JSON string is converted into Data using UTF-8 encoding.
- JSONDecoder: The decoder converts the data into a Swift model.
- Product.self: Specifies the expected type of the decoded value.
- try: Indicates that decoding may throw an error.
- do-catch: Handles decoding failures without crashing the application.
6. Understanding the Encodable Protocol
Encodable allows a Swift value to be converted into an external representation. In iOS development, JSONEncoder is commonly used to encode Swift model objects into JSON data before sending them to a backend API.
For example, a registration screen may collect a user's name, email address, and password. The application can represent the permitted request fields in a Swift model and encode that model into a JSON request body.
6.1 Defining an Encodable Model
import Foundation
struct RegistrationRequest: Encodable {
let name: String
let email: String
let age: Int
}
6.2 Encoding a Swift Object into JSON
import Foundation
let registration = RegistrationRequest(
name: "Priya Mehta",
email: "[email protected]",
age: 24
)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
let jsonData = try encoder.encode(registration)
if let jsonString = String(
data: jsonData,
encoding: .utf8
) {
print(jsonString)
}
} catch {
print("JSON encoding failed:", error)
}
6.3 Possible JSON Output
{
"name" : "Priya Mehta",
"email" : "[email protected]",
"age" : 24
}
The exact key order and formatting may vary. JSON object key ordering should not be relied upon unless a specific format explicitly requires it.
6.4 Common Uses of Encodable
- Submitting registration forms.
- Updating user profile information.
- Sending product details to a server.
- Creating a task or order through an API.
- Encoding structured request bodies for POST, PUT, and PATCH operations.
7. Difference Between Codable, Encodable, and Decodable
| Feature |
Encodable |
Decodable |
Codable |
| Primary purpose |
Convert Swift values into an external representation. |
Create Swift values from an external representation. |
Support both encoding and decoding. |
| JSON utility |
JSONEncoder |
JSONDecoder |
Both utilities |
| Typical use |
Sending API request data. |
Parsing API response data. |
Models used for both requests and responses. |
| Protocol requirements |
Encoding implementation |
Decoding implementation |
Both protocols |
Remember: Encodable means converting a Swift value outward. Decodable means converting external data inward. Codable combines both capabilities.
8. Parsing API Responses Using URLSession
In real iOS applications, JSON usually arrives through network requests rather than hardcoded strings. URLSession can retrieve the response data, while JSONDecoder converts it into the required model.
8.1 Example API Response Model
import Foundation
struct Article: Decodable {
let id: Int
let title: String
let body: String
}
8.2 Fetching and Decoding JSON
import Foundation
func fetchArticle() async throws -> Article {
guard let url = URL(
string: "https://api.example.com/articles/1"
) else {
throw URLError(.badURL)
}
let (data, response) = try await URLSession.shared.data(
from: url
)
guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NSError(
domain: "APIError",
code: httpResponse.statusCode,
userInfo: [
NSLocalizedDescriptionKey:
"The server returned an unsuccessful response."
]
)
}
let article = try JSONDecoder().decode(
Article.self,
from: data
)
return article
}
8.3 Calling the Function
Task {
do {
let article = try await fetchArticle()
print("Title:", article.title)
print("Body:", article.body)
} catch {
print("Unable to load article:", error.localizedDescription)
}
}
This example demonstrates asynchronous networking, HTTP status validation, JSON decoding, and error propagation. Replace the example URL with an actual endpoint that returns a JSON object matching the Article structure.
9. Decoding JSON Arrays
Many APIs return collections of records instead of a single object. JSONDecoder can decode an array directly when the response contains a top-level JSON array.
9.1 Example JSON Array
[
{
"id": 1,
"title": "Introduction to Swift",
"body": "Learn the fundamentals of Swift."
},
{
"id": 2,
"title": "Working with JSON",
"body": "Parse API responses using Codable."
}
]
9.2 Decoding the Array
let articles = try JSONDecoder().decode(
[Article].self,
from: data
)
for article in articles {
print(article.id, article.title)
}
Using [Article].self tells JSONDecoder to expect an array of Article objects. If the server instead wraps the array inside a JSON object, the model must represent that wrapper.
10. Handling Nested JSON Objects
JSON responses often contain objects inside other objects. Swift can represent this structure using nested Codable models.
10.1 Example Nested JSON
{
"id": 101,
"name": "Rahul Sharma",
"address": {
"city": "Mumbai",
"country": "India"
}
}
10.2 Creating Nested Swift Models
import Foundation
struct Address: Decodable {
let city: String
let country: String
}
struct Customer: Decodable {
let id: Int
let name: String
let address: Address
}
10.3 Accessing Nested Values
let customer = try JSONDecoder().decode(
Customer.self,
from: data
)
print(customer.name)
print(customer.address.city)
print(customer.address.country)
The nested Address structure represents the address object in the JSON response. If the API changes the shape of that object, the model may also need to change.
11. Handling Optional and Missing JSON Values
API responses do not always contain every possible field. A value may be optional, missing, or explicitly set to null. Swift optionals can represent fields that may be absent or null.
11.1 Example JSON
{
"id": 101,
"name": "Rahul Sharma",
"phone": null
}
11.2 Optional Properties
struct CustomerProfile: Decodable {
let id: Int
let name: String
let phone: String?
}
When a key is missing or contains JSON null, a synthesized Decodable implementation can decode a compatible optional property as nil. A non-optional property generally must be present and contain a compatible value.
11.3 Accessing Optional Values
if let phone = profile.phone {
print("Phone:", phone)
} else {
print("Phone number is not available.")
}
Optional properties should be used only when the application can correctly handle the value being absent. Making every property optional can hide important data-contract errors.
12. Mapping JSON Keys to Swift Property Names
Some APIs use naming conventions that differ from Swift. For example, a server may return snake_case keys while the Swift model uses camelCase property names.
12.1 Example JSON
{
"user_id": 101,
"full_name": "Rahul Sharma",
"email_address": "[email protected]"
}
12.2 Using CodingKeys
struct UserProfile: Codable {
let userId: Int
let fullName: String
let emailAddress: String
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case fullName = "full_name"
case emailAddress = "email_address"
}
}
CodingKeys explicitly maps JSON keys to Swift properties. It is especially useful when API field names contain abbreviations, unusual capitalization, reserved words, or naming conventions that should not be reflected in the Swift model.
12.3 Using Key Decoding Strategies
When an API consistently uses snake_case, JSONDecoder can convert those keys to camelCase property names automatically.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let profile = try decoder.decode(
UserProfile.self,
from: data
)
When using .convertFromSnakeCase, model property names should follow the converted names. Explicit CodingKeys may be needed for exceptional key mappings; avoid combining custom mappings and automatic conversion without verifying the expected behavior.
13. Working with Dates in JSON
JSON does not define a native date data type. APIs commonly transmit dates as ISO 8601 strings or numeric timestamps. JSONDecoder supports date-decoding strategies that help convert supported formats into Swift Date values.
13.1 Example ISO 8601 Date
{
"id": 1,
"title": "iOS Networking",
"createdAt": "2026-10-29T10:30:00Z"
}
13.2 Decoding ISO 8601 Dates
struct CourseArticle: Decodable {
let id: Int
let title: String
let createdAt: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let article = try decoder.decode(
CourseArticle.self,
from: data
)
The date-decoding strategy must match the format supplied by the API. If the server uses a different timestamp format or fractional seconds, configure an appropriate strategy or provide a custom decoding implementation.
14. Custom Decoding with init(from:)
Sometimes JSON data does not map directly to the Swift model. For example, a field may contain a numeric value that must be transformed, or a model may combine information from multiple JSON keys.
In such cases, a type can implement the Decodable initializer manually.
import Foundation
struct Score: Decodable {
let value: Int
enum CodingKeys: String, CodingKey {
case value = "score_value"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(
keyedBy: CodingKeys.self
)
value = try container.decode(
Int.self,
forKey: .value
)
}
}
This example illustrates custom decoding. For straightforward models, synthesized decoding is usually simpler and easier to maintain.
15. Encoding and Decoding the Same Model
When a model is used both to read API responses and to create request bodies, Codable can be convenient. The model should still accurately reflect the fields required by each API operation.
import Foundation
struct TaskItem: Codable {
let id: Int
var title: String
var isCompleted: Bool
}
let task = TaskItem(
id: 501,
title: "Complete iOS notes",
isCompleted: false
)
do {
let encoder = JSONEncoder()
let jsonData = try encoder.encode(task)
let decoder = JSONDecoder()
let decodedTask = try decoder.decode(
TaskItem.self,
from: jsonData
)
print(decodedTask.title)
print(decodedTask.isCompleted)
} catch {
print("Encoding or decoding failed:", error)
}
This example encodes a TaskItem and then decodes the generated JSON data back into a Swift value. In a production API, response and request models may differ, especially when the server generates identifiers or requires fields that should not be sent by the client.
16. Sending Encoded JSON Through URLSession
After encoding a Swift object with JSONEncoder, the resulting Data can be assigned to a URLRequest body. This is useful for POST, PUT, and PATCH requests when the endpoint accepts JSON.
import Foundation
struct TaskRequest: Encodable {
let title: String
let isCompleted: Bool
}
func createTask() async throws {
guard let url = URL(
string: "https://api.example.com/tasks"
) else {
throw URLError(.badURL)
}
let task = TaskRequest(
title: "Learn Codable",
isCompleted: false
)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
request.setValue(
"application/json",
forHTTPHeaderField: "Accept"
)
request.httpBody = try JSONEncoder().encode(task)
let (_, response) = try await URLSession.shared.data(
for: request
)
guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NSError(
domain: "APIError",
code: httpResponse.statusCode,
userInfo: [
NSLocalizedDescriptionKey:
"Unable to create the task."
]
)
}
print("Task request completed successfully.")
}
This code demonstrates how Encodable and URLSession work together. The endpoint is illustrative, so replace it with a real API and adjust the response handling to the server's contract.
17. Understanding Common JSON Parsing Errors
JSON decoding is type-safe, which means malformed JSON or a mismatch between the response and the Swift model can cause decoding to fail.
| Error Type |
Meaning |
Possible Solution |
| keyNotFound |
A required key is missing. |
Check the JSON response and model property names. |
| typeMismatch |
The JSON value has an incompatible type. |
Match the Swift type to the actual response. |
| valueNotFound |
A required non-optional value is absent or null. |
Verify the response contract and use an optional if appropriate. |
| dataCorrupted |
The data cannot be decoded as the expected representation. |
Check JSON validity, date formats, and custom decoding rules. |
| Invalid URL |
The endpoint URL is invalid. |
Validate the URL before sending the request. |
| HTTP error |
The server returned an unsuccessful status code. |
Inspect the status code and handle the API error response. |
17.1 Printing Detailed Decoding Errors
do {
let user = try JSONDecoder().decode(
User.self,
from: data
)
print(user.name)
} catch let DecodingError.keyNotFound(key, context) {
print("Missing key:", key.stringValue)
print("Path:", context.codingPath)
} catch let DecodingError.typeMismatch(type, context) {
print("Type mismatch:", type)
print("Path:", context.codingPath)
} catch let DecodingError.valueNotFound(type, context) {
print("Missing value:", type)
print("Path:", context.codingPath)
} catch let DecodingError.dataCorrupted(context) {
print("Corrupted data:", context.debugDescription)
} catch {
print("Other error:", error.localizedDescription)
}
Detailed error information is useful during development. In production, avoid exposing internal response data, sensitive information, or technical implementation details directly to end users.
18. Best Practices for JSON Parsing
- Define dedicated Swift models for API request and response data.
- Prefer Codable synthesis for standard JSON structures.
- Use Decodable when the model only needs to read data.
- Use Encodable when the model only needs to send data.
- Use Codable when both directions are required and the same model is appropriate.
- Use optional properties only for fields that may legitimately be absent or null.
- Validate HTTP status codes before decoding a success response.
- Handle empty responses such as HTTP 204 without attempting unnecessary decoding.
- Use CodingKeys for explicit key mapping when required.
- Configure date strategies to match the API's date format.
- Avoid force-unwrapping values received from external sources.
- Keep API credentials and personal data out of logs.
- Use meaningful error handling and show clear feedback in the interface.
- Test decoding with representative success and failure responses.
- Keep networking and data-conversion responsibilities separate from view code.
19. Practical Project: Displaying Articles from an API
Build a small iOS feature that fetches articles from a REST API and displays their titles in a list. This project combines URLSession, Decodable, asynchronous programming, and SwiftUI state management.
Project Requirements
- Fetch article data from a JSON API.
- Decode the response into Article models.
- Display the article titles and descriptions.
- Show a loading indicator while the request is running.
- Display an error message if the request fails.
- Support refreshing the article list.
Example SwiftUI Implementation
import SwiftUI
import Foundation
struct Article: Decodable, Identifiable {
let id: Int
let title: String
let body: String
}
@MainActor
final class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String?
func loadArticles() async {
guard let url = URL(
string: "https://api.example.com/articles"
) else {
errorMessage = "Invalid API URL."
return
}
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
let (data, response) = try await URLSession.shared.data(
from: url
)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
articles = try JSONDecoder().decode(
[Article].self,
from: data
)
} catch {
errorMessage = "Unable to load articles. Please try again."
}
}
}
struct ArticleListView: View {
@StateObject private var viewModel = ArticleViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView("Loading articles...")
} else if let message = viewModel.errorMessage {
VStack(spacing: 12) {
Text(message)
Button("Retry") {
Task {
await viewModel.loadArticles()
}
}
}
} else {
List(viewModel.articles) { article in
VStack(alignment: .leading, spacing: 6) {
Text(article.title)
.font(.headline)
Text(article.body)
.font(.subheadline)
}
}
.refreshable {
await viewModel.loadArticles()
}
}
}
.navigationTitle("Articles")
.task {
await viewModel.loadArticles()
}
}
}
}
Note: This is an illustrative implementation. Replace the placeholder URL with an endpoint that returns a top-level JSON array matching the Article model. A production implementation should also consider request cancellation, empty states, pagination, and more specific error reporting.
20. Practice Exercises
- Create a Codable structure for a student with an ID, name, email, and course.
- Decode a JSON object representing a product into a Swift model.
- Decode an array of users from a JSON response.
- Implement Encodable for a registration request model.
- Map a JSON key named
user_name to a Swift property named userName.
- Decode a nested JSON object containing customer and address information.
- Handle an optional phone number that may be missing or null.
- Decode an ISO 8601 date using JSONDecoder.
- Send an encoded model through a POST request using URLSession.
- Write error-handling code for missing keys and type mismatches.
21. Interview Questions and Answers
Q1. What is Codable in Swift?
Answer: Codable is a type alias combining Encodable and Decodable. It allows compatible Swift types to be encoded into and decoded from external representations.
Q2. What is the difference between Encodable and Decodable?
Answer: Encodable supports converting a Swift value into an external representation, while Decodable supports creating a Swift value from an external representation.
Q3. What is JSONDecoder used for?
Answer: JSONDecoder converts JSON data into Swift values that conform to Decodable.
Q4. What is JSONEncoder used for?
Answer: JSONEncoder converts Encodable Swift values into JSON data.
Q5. Why should developers use Codable instead of manually parsing every JSON field?
Answer: Codable reduces boilerplate, provides type-safe model conversion, and makes standard JSON parsing easier to maintain.
Q6. How can a Swift model handle missing JSON fields?
Answer: Optional properties can represent fields that may be absent or null. Required properties should remain non-optional when the API contract guarantees their presence.
Q7. What is CodingKeys?
Answer: CodingKeys is an enumeration that maps Swift properties to JSON keys and can customize which properties participate in encoding or decoding.
Q8. How do you decode an array of JSON objects?
Answer: Use JSONDecoder with an array type, such as [Product].self, when the JSON response is a top-level array.
Q9. How do you handle date values in JSON?
Answer: Configure JSONDecoder's dateDecodingStrategy or implement custom decoding to match the date format used by the API.
Q10. Why can JSON decoding fail even when the response is valid JSON?
Answer: The response may contain keys or value types that do not match the Swift model, or required fields may be missing or null.
Q11. Can one Swift structure conform to Codable?
Answer: Yes. A structure can conform to Codable when its properties and implementation support both encoding and decoding.
Q12. Why should an app validate HTTP status codes before decoding?
Answer: A server can return an error response with a different JSON structure. Checking the status code helps the application handle API failures separately from successful responses.
22. Quick Revision Checklist
- JSON represents structured information using objects, arrays, and primitive values.
- Codable combines Encodable and Decodable.
- JSONDecoder converts JSON data into Swift models.
- JSONEncoder converts Swift models into JSON data.
- URLSession retrieves data from remote APIs.
- Optional properties can represent missing or null fields.
- CodingKeys supports custom key mapping.
- Date strategies control how date values are decoded and encoded.
- HTTP status validation and error handling are essential for reliable networking.
- SwiftUI views should reflect loading, success, and failure states.
23. Conclusion
Parsing JSON responses is a fundamental skill in iOS app development. Swift's Codable system makes it easier to represent API data using strongly typed models and reduces the need for repetitive manual parsing.
Encodable is useful when sending information to a server, Decodable is useful when receiving information from a server, and Codable supports both operations. When combined with URLSession, JSONDecoder, JSONEncoder, and proper error handling, these protocols provide a reliable foundation for networking features in iOS applications.
Practice with simple JSON objects first, then move on to arrays, nested structures, optional fields, custom key mappings, date conversion, and complete API-driven SwiftUI projects.
24. Learn More with JustAcademy
Explore additional iOS app development topics and strengthen your practical Swift programming skills through these resources.